package statistics

import (
	"fmt"
	"github.com/Unixeno/TheMoon/model"
	"github.com/Unixeno/TheMoon/planet/config"
	_ "github.com/influxdata/influxdb1-client" // this is important because of the bug in go mod
	client "github.com/influxdata/influxdb1-client/v2"
	"strings"
	"time"
)

type Dao struct {
	influxDB   client.Client
	Statistics *model.Statistics
	MoonID     string
}

func Init() (err error) {
	influxDBClient, err := client.NewHTTPClient(client.HTTPConfig{
		Addr: config.SysConfig.Statistics.InfluxDB,
	})
	if err == nil {
		_, err = influxDBClient.Query(client.NewQuery("create database \"statistics\"", "", ""))
	}
	return
}

type BasicStats struct {
	// Values列表内对应数据的含义
	Columns []string
	// 按时间戳排序的数据列
	Values [][]interface{}
}

type Stats struct {
	// 系统信息
	System BasicStats
	// 网络信息
	Network map[string]BasicStats // key是网络接口名
	// 磁盘信息
	Disk map[string]BasicStats // 磁盘统计信息
}

type MoonStatsParam struct {
	// 统计数据集，以“,”分割，为空时获取所有的统计类别。目前支持system, network, disk
	DataSet string
	// 查询限制
	Limit int
	// 是否按时间倒序查询
	Desc bool
	// 不查询此时间之前的数据
	NotBefore uint32
	// 不查询此时间之后的数据
	NotAfter uint32
	// 网卡的名字，以,分割，为空时获取所有网卡的流量信息
	NetDev string
	// 需要查询的磁盘名字，为空时获取所有磁盘的统计信息
	Disks string

	order     string // order desc
	whereTime string
}

func (param *MoonStatsParam) Parse() {
	if param.Desc {
		param.order = " order by time desc"
	}
	if param.NotAfter == 0 {
		param.NotAfter = uint32(time.Now().Unix())
	}
	param.whereTime = fmt.Sprintf(" (time > %ds) and (time < %ds)", param.NotBefore, param.NotAfter)
}

func New() *Dao {
	influxDBClient, err := client.NewHTTPClient(client.HTTPConfig{
		Addr: config.SysConfig.Statistics.InfluxDB,
	})
	if err == nil {
		return &Dao{
			influxDB: influxDBClient,
		}
	}
	return nil
}

func (dao *Dao) Close() error {
	return dao.influxDB.Close()
}

func (dao *Dao) Store() error {
	bp, _ := client.NewBatchPoints(client.BatchPointsConfig{
		Precision: "s",
		Database:  config.SysConfig.Statistics.DBName,
	})

	sysPoint, _ := client.NewPoint(
		"system",
		map[string]string{"moon": dao.MoonID},
		map[string]interface{}{
			"cpu":     dao.Statistics.CPUPercent,
			"mem":     dao.Statistics.MemoryPercent,
			"mem_avl": int64(dao.Statistics.MemoryAvailable),
			"load1":   dao.Statistics.Load1,
			"load5":   dao.Statistics.Load5,
			"load15":  dao.Statistics.Lode15,
		}, time.Now())
	bp.AddPoint(sysPoint)

	for name, val := range dao.Statistics.NetStat {
		point, _ := client.NewPoint("network",
			map[string]string{"moon": dao.MoonID, "dev": name},
			map[string]interface{}{
				"send": int64(val.BytesSentDelta),
				"recv": int64(val.BytesRecvDelta),
			}, time.Now())
		bp.AddPoint(point)
	}

	for name, val := range dao.Statistics.DiskStat {
		point, _ := client.NewPoint("disk",
			map[string]string{"moon": dao.MoonID, "dev": name},
			map[string]interface{}{
				"write": int64(val.BytesWriteDelta),
				"read":  int64(val.BytesReadDelta),
				"io":    val.IOPercent,
			}, time.Now())
		bp.AddPoint(point)
	}
	return dao.influxDB.Write(bp)
}

func (dao *Dao) GetStats(param MoonStatsParam) (stats Stats, err error) {
	param.Parse()
	sets := strings.Split(param.DataSet, ",")
	if len(sets) == 1 || sets[0] == "" {
		sets = []string{"system", "network", "disk"}
	}

	for _, set := range sets {
		switch set {
		case "system":
			if sysStats, err := dao.querySystem(param); err != nil {
				return stats, err
			} else {
				stats.System = sysStats
			}
		case "network":
			if netStats, err := dao.queryNetwork(param); err != nil {
				return stats, err
			} else {
				stats.Network = netStats
			}
		case "disk":
			if diskStats, err := dao.queryDisk(param); err != nil {
				return stats, err
			} else {
				stats.Disk = diskStats
			}
		}
	}
	return
}

func (dao *Dao) querySystem(param MoonStatsParam) (stats BasicStats, err error) {
	queryString := fmt.Sprintf("select *::field FROM system where (moon = '%s') and %s %s limit %d",
		dao.MoonID, param.whereTime, param.order, param.Limit)
	fmt.Println(queryString)

	resp, err := dao.influxDB.Query(client.NewQuery(queryString, config.SysConfig.Statistics.DBName, "s"))
	if err != nil {
		return stats, err
	}
	if resp.Error() != nil {
		return stats, resp.Error()
	}
	if len(resp.Results) == 0 {
		return
	}
	stats.Columns = resp.Results[0].Series[0].Columns
	stats.Values = resp.Results[0].Series[0].Values
	return
}

func (dao *Dao) queryNetwork(param MoonStatsParam) (stats map[string]BasicStats, err error) {
	stats = map[string]BasicStats{}

	queryString := fmt.Sprintf("select *::field FROM network where (moon = '%s') and %s group by dev %s limit %d",
		dao.MoonID, param.whereTime, param.order, param.Limit)

	resp, err := dao.influxDB.Query(client.NewQuery(queryString, config.SysConfig.Statistics.DBName, "s"))
	if err != nil {
		return stats, err
	}
	if resp.Error() != nil {
		return stats, resp.Error()
	}
	if len(resp.Results) == 0 {
		return
	}
	for _, s := range resp.Results[0].Series {
		stats[s.Tags["dev"]] = BasicStats{
			Columns: s.Columns,
			Values:  s.Values,
		}
	}
	return
}

func (dao *Dao) queryDisk(param MoonStatsParam) (stats map[string]BasicStats, err error) {
	stats = map[string]BasicStats{}
	queryString := fmt.Sprintf("select *::field FROM disk where (moon = '%s')and%s group by dev %s limit %d",
		dao.MoonID, param.whereTime, param.order, param.Limit)
	fmt.Println(queryString)

	resp, err := dao.influxDB.Query(client.NewQuery(queryString, config.SysConfig.Statistics.DBName, "s"))
	if err != nil {
		return stats, err
	}
	if resp.Error() != nil {
		return stats, resp.Error()
	}
	if len(resp.Results) == 0 {
		return
	}

	for _, s := range resp.Results[0].Series {
		stats[s.Tags["dev"]] = BasicStats{
			Columns: s.Columns,
			Values:  s.Values,
		}
	}
	return
}
